﻿namespace Cellfish.Toolbox.Tasks
{
    using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Threading;
    using System.Threading.Tasks;

    /// <summary>
    /// Extension methods to add progress reporting to collections of tasks.
    /// </summary>
    public static class WithProgressExtensions
    {
        /// <summary>
        /// Adds progress report on a collection of tasks. Progress is reported as number of tasks completed.
        /// I.e: 1, 2, 3 etc.
        /// </summary>
        /// <remarks>The progress reporting is guaranteed to be in order from one (1) and increasing.</remarks>
        /// <param name="tasks">The tasks to add progress to.</param>
        /// <param name="progress">The progress reporting object.</param>
        /// <returns>A collection of tasks representing the original tasks with progress reporting added.
        /// This means that when a task is completed in the resulting collection then progress has been reported.</returns>
        /// <exception cref="System.ArgumentNullException">If tasks is null.</exception>
        /// <exception cref="System.ArgumentNullException">If progress is null.</exception>
        public static IEnumerable<Task> WithProgress(this IEnumerable<Task> tasks, IProgress<int> progress)
        {
            if (progress == null)
            {
                throw new ArgumentNullException("progress");
            }

            int completed = 0;
            var completedLocked = new object();
            var progressAdapter = new ProgressSafe<Task>(
                t =>
                    {
                        lock (completedLocked)
                        {
                            progress.Report(++completed);
                        }
                    });
            return tasks.WithProgress(progressAdapter);
        }

        /// <summary>
        /// Adds progress report on a collection of tasks. Progress is reported by giving the completed task.
        /// </summary>
        /// <remarks>Note that progress may be reported out of order due to task scheduling.
        /// This means progress for the first completed task may be reported after the saecond completed task.</remarks>
        /// <param name="tasks">The tasks to add progress to.</param>
        /// <param name="progress">The progress reporting object.</param>
        /// <returns>A collection of tasks representing the original tasks with progress reporting added.
        /// This means that when a task is completed in the resulting collection then progress has been reported.</returns>
        /// <exception cref="System.ArgumentNullException">If tasks is null.</exception>
        /// <exception cref="System.ArgumentNullException">If progress is null.</exception>
        public static IEnumerable<Task> WithProgress(this IEnumerable<Task> tasks, IProgress<Task> progress)
        {
            if (tasks == null)
            {
                throw new ArgumentNullException("tasks");   
            }

            if (progress == null)
            {
                throw new ArgumentNullException("progress");
            }

            return tasks.Select(
                task =>
                    {
                        var tcs = new TaskCompletionSource<bool>();
                        task.ContinueWith(
                            t =>
                                {
                                    progress.Report(t);
                                    if (t.IsCanceled)
                                    {
                                        tcs.TrySetCanceled();
                                    }
                                    else if (t.IsFaulted)
                                    {
                                        tcs.TrySetException(t.Exception.InnerException);
                                    }
                                    else
                                    {
                                        tcs.TrySetResult(true);
                                    }
                                },
                            TaskContinuationOptions.ExecuteSynchronously);
                        return (Task)tcs.Task;
                    });
        }

        /// <summary>
        /// Adds progress report on a collection of tasks. Progress is reported by giving the result of successful tasks.
        /// </summary>
        /// <remarks>Note that progress may be reported out of order due to task scheduling.
        /// This means progress for the first completed task may be reported after the saecond completed task.</remarks>
        /// <typeparam name="T">The result of the tasks.</typeparam>
        /// <param name="tasks">The tasks to add progress to.</param>
        /// <param name="progress">The progress reporting object.</param>
        /// <returns>A collection of tasks representing the original tasks with progress reporting added.
        /// This means that when a task is completed in the resulting collection then progress has been reported.</returns>
        /// <exception cref="System.ArgumentNullException">If tasks is null.</exception>
        /// <exception cref="System.ArgumentNullException">If progress is null.</exception>
        public static IEnumerable<Task<T>> WithProgress<T>(this IEnumerable<Task<T>> tasks, IProgress<T> progress)
        {
            if (progress == null)
            {
                throw new ArgumentNullException("progress");
            }

            var progressAdapter = new ProgressSafe<Task<T>>(
                t =>
                    {
                        if (t.Status == TaskStatus.RanToCompletion)
                        {
                            progress.Report(t.Result);
                        }
                    });
            return tasks.WithProgress(progressAdapter);
        }

        /// <summary>
        /// Adds progress report on a collection of tasks. Progress is reported by giving the completed task.
        /// </summary>
        /// <remarks>Note that progress may be reported out of order due to task scheduling.
        /// This means progress for the first completed task may be reported after the saecond completed task.</remarks>
        /// <typeparam name="T">The result of the tasks.</typeparam>
        /// <param name="tasks">The tasks to add progress to.</param>
        /// <param name="progress">The progress reporting object.</param>
        /// <returns>A collection of tasks representing the original tasks with progress reporting added.
        /// This means that when a task is completed in the resulting collection then progress has been reported.</returns>
        /// <exception cref="System.ArgumentNullException">If tasks is null.</exception>
        /// <exception cref="System.ArgumentNullException">If progress is null.</exception>
        public static IEnumerable<Task<T>> WithProgress<T>(this IEnumerable<Task<T>> tasks, IProgress<Task<T>> progress)
        {
            if (tasks == null)
            {
                throw new ArgumentNullException("tasks");
            }

            if (progress == null)
            {
                throw new ArgumentNullException("progress");
            }

            return tasks.Select(
                task =>
                    {
                        var tcs = new TaskCompletionSource<T>();
                        task.ContinueWith(
                            t =>
                                {
                                    progress.Report(t);
                                    if (t.IsCanceled)
                                    {
                                        tcs.TrySetCanceled();
                                    }
                                    else if (t.IsFaulted)
                                    {
                                        tcs.TrySetException(t.Exception.InnerException);
                                    }
                                    else
                                    {
                                        tcs.TrySetResult(t.Result);
                                    }
                                },
                            TaskContinuationOptions.ExecuteSynchronously);
                        return tcs.Task;
                    });
        }
    }
}
